/**
 * 
 */
package smallChanges.statements;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import net.sourceforge.pmd.ast.SimpleJavaNode;
import smallChanges.Tokenization.SCToken;
import smallChanges.Tokenization.TokenSet;
import smallChanges.util.SCConstants;

/**
 * @author jspacco
 *
 */
public abstract class Statement extends MultiTokenCodeRange2D implements SCConstants
{
	private Long statementID;
	private Long parentID;
	//private Long classID;
	private Long fileID;
	private Long transID;
	// XXX maybe replace stmtType with enum?
	private String stmtType;
	private Statement parent;
	private List<MetaInformationAnnotation> annotations=
	    new LinkedList<MetaInformationAnnotation>();
	
	public static final String INDENT_TAB_SIZE="    ";
		
	public Statement(TokenSet allTokensInClass) {
		super(allTokensInClass);
	}
	
	public Statement() {
	    super(null);
	}
	
	public void addMetaInfo(MetaInformationAnnotation annotation) {
	    annotations.add(annotation);
	}
	
	public void addAllMetaInfo(Collection<MetaInformationAnnotation> collection)
	{
	    for (MetaInformationAnnotation metaInfo : collection) {
	        addMetaInfo(metaInfo);
	    }
	}
	
	public List<MetaInformationAnnotation> getMetaInformationAnnotations() {
	    return annotations;
	}
	
	
	
	protected Statement(String type, TokenSet allTokensInClass) {
		this(allTokensInClass);
		stmtType=type;
	}
	
	@Override
	public void setCoordinatesFromNode(SimpleJavaNode node)
	{
		super.setCoordinatesFromNode(node);
		setTokensFromRange();
	}
	
	public void addToken(SCToken token) {
		tokensInRange.add(token);
	}
    
    public List<Statement> flatten() {
        List<Statement> list=new LinkedList<Statement>();
        return flattenHelper(list);
    }
    
    List<Statement> flattenHelper(List<Statement> list)
    {
        list.add(this);
        return list;
    }
    
    protected TokenSet getPreceedingTokens()
    {
        return allTokensInClass.getPrecedingTokens(beginLine, beginColumn, endLine, endColumn);
    }
    
	/**
	 * Convert a statement into its "image", which is a string representation
	 * of the collection of tokens making up the image.
	 * @return
	 */
	public abstract String getImage();

	@Override
	public String toString()
	{
	    return getImage();
	}
	
	/**
	 * Convert the statement into a formatted String, including
	 * padding.
	 * 
	 * @return
	 */
	public String toFormattedString()
	{
	    return toStringHelper(0);
	}
	
	/**
	 * Helper method that converts a statement into a String, but takes into
	 * account the nesting depth.
	 * @param depth
	 * @return
	 */
	abstract String toStringHelper(int depth);
	
	/**
	 * Helper method that creates a given number of spaces of padding.  This method
	 * can be used to simulate tab characters. 
	 * @param depth
	 * @return
	 */
	static String makePadding(int depth)
	{
		StringBuffer buf=new StringBuffer();
		for (int ii=0; ii < depth; ii++) {
			buf.append(INDENT_TAB_SIZE);
		}
		return buf.toString();
	}

	public static String removeSemiColon(String stmt)
	{
		String newStmt = new String(stmt);
	    if(stmt.endsWith(";"))
	    {
	    	newStmt = stmt.substring(0, stmt.length()-1);
	    }
	    return newStmt;
	}
	/**
	 * @param other the other Statement to compare against
	 * @return the normalized Levenstein edit distance
	 */
	public double getNormalizedLevenshteinEditDistance(Statement other)
	{
		double nEditDist=1.0;
		String thisStatementImage, otherStatementImage;

		if (this.getTokensInRange() != null && other.getTokensInRange() != null )
		{
		    thisStatementImage = this.getImage();
		    otherStatementImage = other.getImage();
		    
		    thisStatementImage = removeSemiColon(thisStatementImage);
		    otherStatementImage = removeSemiColon(otherStatementImage);
		    
		    nEditDist = org.apache.commons.lang.StringUtils.getLevenshteinDistance(thisStatementImage, otherStatementImage);
		    nEditDist /= ( java.lang.Math.max(thisStatementImage.length(), otherStatementImage.length()));
		}

		return nEditDist;
	}
	
	private class TokenHashMap extends java.util.HashMap<String, String>
	{
		String currChar="!";
		
		public String put(String key) throws Exception
		{
			String value = currChar;
			String retValue = currChar;
			char charKey;
			
			charKey = currChar.charAt(0);
			if( charKey > 126 )
			{
				throw new Exception("TooManyTokens");
			}

			put(key, value);
			
			currChar = String.valueOf(++charKey);
			return retValue;
		}
		
	}
	
	public double getNormalizedCosineDistance(Statement other)
	{
	    return -1.0;
	}
	
	/**
	 * @param other the other Statement to compare against
	 * @return the normalized Levenstein edit distance based on Tokens and not characters
	 */
	public double getNormalizedLevenshteinTokenEditDistance(Statement other)
	{
		double nEditDist=1.0;
		String thisStatementImage, otherStatementImage;
		
		/* to do this, I'm going to turn each token in to a single character
		 * so that I can use the Apache common implementation of getLevenshteinDistance().
		 * The token will be turned into a character in the range ASCII 33 (!) to ASCII 126 (~)
		 */
		TokenHashMap tokenMap = new TokenHashMap();
		
		thisStatementImage = this.getImageCharacterForTokens(tokenMap);
		otherStatementImage = other.getImageCharacterForTokens(tokenMap);
		
		if( thisStatementImage != null && otherStatementImage != null)
		{
			nEditDist = org.apache.commons.lang.StringUtils.getLevenshteinDistance(thisStatementImage, otherStatementImage);
			nEditDist /= ( (double) java.lang.Math.max(thisStatementImage.length(), otherStatementImage.length()));
		}
		return nEditDist;
	}

	/**
	 * @param stmt the statement for which to produce the image
	 * @param tokenMap the unique token map that matches tokens with single characters
	 * @return the actual java statement with a single character substituted consistently for each token
	 */
	private String getImageCharacterForTokens(TokenHashMap tokenMap)
	{
		String result="";
		String value;
		Statement theStatement = this;
		
		if ( this instanceof CompoundStatement)
		{
		    theStatement = ((CompoundStatement) this).getHeaderStmt();
		}

		/* get the token set from either this statement or this statement's
		 * header.
		 */
		TokenSet ts = theStatement.getTokensInRange();
		
	    if ( ts != null )
	    {
          // System.err.println("BEGIN TOKEN");
	        for (SCToken t : ts) {
	            //System.err.println("TOKEN "+ t.getImage());
	        	
	        	if( !t.getImage().equals(";"))
	        	{
	        		if(tokenMap.containsKey(t.getImage()))
	        		{
	        			value = (String) tokenMap.get(t.getImage());
	        		}
	        		else
	        		{
	        			try
	        			{
	        				value = tokenMap.put(t.getImage());
	        			}
	        			catch(Exception e)
	        			{
	        				return null;
	        			}
	        		}
	        		result += value;
	        	}
	        }
	        //System.err.println("END TOKEN");
	    }

		return result;
	}
	
	
	/**
	 * @return the actual java statement as a string without whitespace
	 */
	/*public String getImage()
	{
		String theImage="";
		
		for (SCToken t : tokenList) {
            theImage += t.getImage();
        }
		return theImage;
	}*/
	
	/**
	 * @return the actual java statement as a string with whitespace
	 */
	public String getPrettyImage()
	{
		String theImage="";
		
		/*for (SCToken t : tokenList) {
            theImage += t.getImage();
            theImage += " ";
        }*/
		return theImage;
	}

	public String getStmtType()
    {
        return stmtType;
    }
    public void setStmtType(String stmtType)
    {
        this.stmtType = stmtType;
    }
	public Long getStatementID()
    {
        return statementID;
    }
    public void setStatementID(Long statementID)
    {
        this.statementID = statementID;
    }
    public Long getParentID()
    {
        return parentID;
    }
    public void setParentID(Long parentID)
    {
        this.parentID = parentID;
    }
    public Long getFileID()
    {
        return fileID;
    }
    public void setFileID(Long fileID)
    {
        this.fileID = fileID;
    }
    public Long getTransID()
    {
        return transID;
    }
    public void setTransID(Long transID)
    {
        this.transID = transID;
    }
    public int compareTo(Statement other)
	{
	    return getImage().compareTo(other.getImage());
	}
    public Statement getParent()
    {
        return parent;
    }

    public void setParent(Statement parent)
    {
        this.parent = parent;
    }

    public void setCoordinatesFromToken(SCToken elseToken)
    {
        setBeginLine(elseToken.getBeginLine());
        setBeginColumn(elseToken.getBeginColumn());
        setEndLine(elseToken.getEndLine());
        setEndColumn(elseToken.getEndColumn());
        setTokensFromRange();
    }
    
}
